package main

import (
	"testing"
)

// TestNumberPredicatesAndOperations tests the number predicates and operations
// implemented in builtins.go: odd?, even?, gcd, lcm
func TestNumberPredicatesAndOperations(t *testing.T) {
	env := NewGlobalEnvironment()

	// Test cases for odd?
	testOdd := []struct {
		input    string
		expected string
	}{{
		input:    "(odd? 1)",
		expected: "#t",
	}, {
		input:    "(odd? 2)",
		expected: "#f",
	}, {
		input:    "(odd? 0)",
		expected: "#f",
	}, {
		input:    "(odd? -1)",
		expected: "#t",
	}, {
		input:    "(odd? -2)",
		expected: "#f",
	}, {
		input:    "(odd? 1001)",
		expected: "#t",
	}, {
		input:    "(odd? 1000)",
		expected: "#f",
	}, {
		// Non-integer number should return #f
		input:    "(odd? 1.5)",
		expected: "#f",
	}, {
		// Non-numeric argument should return #f
		input:    "(odd? #t)",
		expected: "#f",
	}, {
		input:    "(odd? \"string\")",
		expected: "#f",
	}}

	// Test cases for even?
	testEven := []struct {
		input    string
		expected string
	}{{
		input:    "(even? 1)",
		expected: "#f",
	}, {
		input:    "(even? 2)",
		expected: "#t",
	}, {
		input:    "(even? 0)",
		expected: "#t",
	}, {
		input:    "(even? -1)",
		expected: "#f",
	}, {
		input:    "(even? -2)",
		expected: "#t",
	}, {
		input:    "(even? 1001)",
		expected: "#f",
	}, {
		input:    "(even? 1000)",
		expected: "#t",
	}, {
		// Non-integer number should return #f
		input:    "(even? 1.5)",
		expected: "#f",
	}, {
		// Non-numeric argument should return #f
		input:    "(even? #t)",
		expected: "#f",
	}, {
		input:    "(even? \"string\")",
		expected: "#f",
	}}

	// Test cases for gcd
	testGcd := []struct {
		input    string
		expected string
	}{{
		input:    "(gcd 12 8)",
		expected: "4",
	}, {
		input:    "(gcd 12 18)",
		expected: "6",
	}, {
		input:    "(gcd 13 17)",
		expected: "1",
	}, {
		input:    "(gcd 0 5)",
		expected: "5",
	}, {
		input:    "(gcd 5 0)",
		expected: "5",
	}, {
		input:    "(gcd -12 8)",
		expected: "4",
	}, {
		input:    "(gcd 12 -8)",
		expected: "4",
	}, {
		// Multiple arguments
		input:    "(gcd 120 84 36)",
		expected: "12",
	}, {
		// All zeros
		input:    "(gcd 0 0)",
		expected: "0",
	}}

	// Test cases for lcm
	testLcm := []struct {
		input    string
		expected string
	}{{
		input:    "(lcm 12 8)",
		expected: "24",
	}, {
		input:    "(lcm 12 18)",
		expected: "36",
	}, {
		input:    "(lcm 13 17)",
		expected: "221",
	}, {
		input:    "(lcm 0 5)",
		expected: "0",
	}, {
		input:    "(lcm 5 0)",
		expected: "0",
	}, {
		input:    "(lcm -12 8)",
		expected: "24",
	}, {
		input:    "(lcm 12 -8)",
		expected: "24",
	}, {
		// Multiple arguments
		input:    "(lcm 4 6 8)",
		expected: "24",
	}}

	// Test cases with rational numbers
	testRational := []struct {
		input    string
		expected string
	}{{
		// 3/2 is not an integer, so odd? should return #f
		input:    "(odd? 3/2)",
		expected: "#f",
	}, {
		// 4/2 is 2, an even integer, so even? should return #t
		input: "(even? 4/2)",
		// The rational 4/2 should be simplified to integer 2
		expected: "#t",
	}, {
		// 9/3 is 3, 6/2 is 3, so gcd should be 3
		input:    "(gcd 9/3 6/2)",
		expected: "3",
	}, {
		// 9/3 is 3, 6/2 is 3, so lcm should be 3
		input:    "(lcm 9/3 6/2)",
		expected: "3",
	}}

	// Run all tests
	runTests(t, env, testOdd)
	runTests(t, env, testEven)
	runTests(t, env, testGcd)
	runTests(t, env, testLcm)
	runTests(t, env, testRational)
}

// Helper function to run tests
func runTests(t *testing.T, env *Environment, tests []struct{ input, expected string }) {
	for _, test := range tests {
		result, err := EvalString(test.input, env)
		if err != nil {
			t.Errorf("Error evaluating %s: %v", test.input, err)
			continue
		}

		if result.String() != test.expected {
			t.Errorf("Expected %s, got %s for input %s", test.expected, result.String(), test.input)
		}
	}
}
